#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Weazel.Gezel.Simulator.Reference
{
  /// <summary>
  /// The finite state machine-style controller controls
  /// the execution of a datapath by determining which action
  /// to execute from the set of possible transitions for a 
  /// given state. Values from the registers of the datapath
  /// may be inspected to determine which transitino to take.
  /// </summary>
  class FsmController : AbstractController
  {
    /// <summary>
    /// original fsm id
    /// </summary>
    private Symid parent;

    /// <summary>
    /// Initial state of the fsm
    /// </summary>
    private Symid initialState;

    /// <summary>
    /// Current fsm state
    /// </summary>
    private Symid currentState;

    /// <summary>
    /// Current condition - only used when
    /// programming the FsmController prior 
    /// to simulation
    /// </summary>
    private Symid currentCondition;

    /// <summary>
    /// Current control step - only used when
    /// programming the FsmController prior 
    /// to simulation
    /// </summary>
    private int currentControlStep;

    /// <summary>
    /// Used during simulation to determine which
    /// instruction was executed previously
    /// </summary>
    private int previousInstructionId;

    /// <summary>
    /// Stores the finite set of states
    /// </summary>
    private List<Symid> states;

    /// <summary>
    /// Stores the set of transitions keyed on the
    /// state which they are associated with
    /// </summary>
    private Dictionary<Symid, List<FsmTransition>> transitions;

    /// <summary>
    /// Maps a transition to a set of datapath instruction i.e.
    /// the set of instructions on the controlled datapath which
    /// should be executed when the transition is taken
    /// </summary>
    private Dictionary<int, DatapathInstruction> instructions;

    /// <summary>
    /// Stores the set of conditions keyed the
    /// transition to which they are associated
    /// </summary>
    private Dictionary<Symid, AbstractSignal> conditions;

    /// <summary>
    /// The set of signals newly created to 
    /// be used for fsm condition evaluation
    /// </summary>
    private List<AbstractSignal> newSignals;

    /// <summary>
    /// Each control step which are contained
    /// in this list will be traced during
    /// execution
    /// </summary>
    private List<int> tracer;

    /// <summary>
    /// Stores a reference to the simulation
    /// to which this controller belongs. Used
    /// to lookup symbols during tracing.
    /// </summary>
    private Simulation simulation;

    /// <summary>
    /// Constructs a new finite state machine controller attacted
    /// the the specified datapath context
    /// </summary>
    /// <param name="datapathContext"></param>
    /// <param name="parent"></param>
    public FsmController(Simulation sim, DatapathContext datapathContext, Symid parent, string name) : base(datapathContext, name)
    {
      this.simulation = sim;
      this.initialState = currentState = Symid.NOSYMBOL;
      this.currentCondition = Symid.NOSYMBOL;
      this.currentControlStep = 0;
      this.parent = parent;
      this.states = new List<Symid>();
      this.conditions = new Dictionary<Symid, AbstractSignal>();
      this.instructions = new Dictionary<int, DatapathInstruction>();
      this.transitions = new Dictionary<Symid, List<FsmTransition>>();
      this.newSignals = new List<AbstractSignal>();
      this.tracer = new List<int>();
    }

    /// <summary>
    /// Add a datapath instruction to be executed in the
    /// current control step being programmed
    /// </summary>
    /// <param name="s"></param>
    internal override void AddSignalFlowGraph(Symid s)
    {
      // make sure the instance exists
      if (!instructions.ContainsKey(currentControlStep))
        // ... and create it if nessesary
        instructions[currentControlStep] = new DatapathInstruction();
      instructions[currentControlStep].AddSignalFlowGraph(s);
    }

    /// <summary>
    /// Sets the initial state of the fsm controller
    /// </summary>
    /// <param name="v"></param>
    public override void AddInitialState(Symid v)
    {
      initialState = v;
    }

    /// <summary>
    /// Adds a new (non-initial) state the the fsm controller
    /// </summary>
    /// <param name="v"></param>
    public override void AddState(Symid v)
    {
      states.Add(v);
    }

    /// <summary>
    /// Instructs the fsm to trace the
    /// current control step 
    /// </summary>
    public void AddTracer()
    {
      tracer.Add(currentControlStep);
    }

    /// <summary>
    /// Adds a new control step to the finite
    /// state machine controller
    /// </summary>
    /// <param name="seq"></param>
    /// <param name="cond">id of the condition associated with this control step</param>
    internal override void ProgramNextstep(int seq, Symid cond)
    {
      currentControlStep = seq;
      currentCondition = cond;
    }

    /// <summary>
    /// Resets the execution state of the finite 
    /// state machine controller
    /// </summary>
    public override void Reset()
    {
      if (initialState == Symid.NOSYMBOL)
      {
        throw new ApplicationException("misssing initial state");
      }
      currentState = initialState;
      previousInstructionId = -1; // hold most recently executed instructions[] index
    }

    /// <summary>
    /// Determines the next control step to take
    /// from the set of possible transitions and
    /// executes the associated datapath instructions
    /// and possibly tracing the transition
    /// </summary>
    /// <param name="g"></param>
    public override void RunNextStep(long generation)
    {
      foreach (FsmTransition transition in transitions[currentState])
      {
        Symid targetState;
        int instructionId;
        if (transition.ShouldTakeTransition(generation, out targetState, out instructionId))
        {
          datapathContext.CurrentControlStep = instructionId;
          instructions[instructionId].Select(datapathContext); // switch dpcontext to execute sfg in this instruction

          // trace facility
          if (tracer.Contains(instructionId))
          {
            simulation.buffer.AppendFormat("{0}: {1} -> {2}\n",
              simulation.symbolNames[parent],
              simulation.symbolNames[currentState],
              simulation.symbolNames[targetState]
              );
          }

//          // for global sleep
//          if (instructionId != previousInstructionId)
//          {
//            Simulation.sleep.CannotSleep();
//          }

          currentState = targetState;
          previousInstructionId = instructionId;
          break;
        }
      }
    }

    /// <summary>
    /// Adds a new unconditional transition to the FsmController during setup
    /// </summary>
    /// <param name="from">origin state</param>
    /// <param name="to">destination state</param>
    public override void programTransition(Symid from, Symid to)
    {
      // creates the state transition belonging to the
      // currentcondition and currentctlstep
      if (!states.Contains(from))
        // in the error message, we give the taget state as context symbol (since
        // the source state symbol does not exist
        throw new ApplicationException("error, cant find source state");

      if (!states.Contains(to))
        // in the error message, we give the source state as context symbol (since
        // the target state symbol does not exist
        throw new ApplicationException("error, cant find target state");

      // create transition
      FsmTransition p;
      if (currentCondition == Symid.NOSYMBOL)
        p = new FsmTransition(to, null, currentControlStep);
      else
      {
        if (!conditions.ContainsKey(currentCondition))
          throw new ApplicationException("internal error, no current condition");
        p = new FsmTransition(to, conditions[currentCondition], currentControlStep);
      }

      // make sure the instance exists
      if (!transitions.ContainsKey(from))
        transitions[from] = new List<FsmTransition>();
      transitions[from].Add(p);
    }

    /// <summary>
    /// Adds a condition to a transition such that the transition is only taken
    /// if the associated condition evaluates to true
    /// </summary>
    /// <param name="parentCondition">parent condition or Symid.NOSYMBOL if no parent condition</param>
    /// <param name="transition">the transition to which the condition is associated</param>
    /// <param name="conditionSignal">condition which should evaluate to a true value</param>
    public void ProgramTrueBranch(Symid parentCondition, Symid transition, AbstractSignal conditionSignal, Simulation sim)
    {
      AbstractSignal a;
      if (parentCondition != Symid.NOSYMBOL)
        // combine this condition with the parent condition
          a = new AndSignal(Symid.NOSYMBOL, conditions[parentCondition], conditionSignal, sim);
      else
        // this should be a 'pass' operation
        a = new ProxySignal(Symid.NOSYMBOL, conditionSignal, 0, 0);
      newSignals.Add(a);
      conditions[transition] = a;
    }

    /// <summary>
    /// Adds a condition to a transition such that the transition is only taken
    /// if the associated condition evaluates to false
    /// </summary>
    /// <param name="parentCondition">parent condition or Symid.NOSYMBOL if no parent condition</param>
    /// <param name="transition">the transition to which the condition is associated</param>
    /// <param name="conditionSignal">condition which should evaluate to a false value</param>
    public void ProgramFalseBranch(Symid parent, Symid transition, AbstractSignal conditionSignal, Simulation sim)
    {
      AbstractSignal s1, a;
      if (parent != Symid.NOSYMBOL)
      {
        // negate and combine this condition with the parent condition
        s1 = new NotSignal(Symid.NOSYMBOL, conditionSignal, sim);
        newSignals.Add(s1);
        a = new AndSignal(Symid.NOSYMBOL, conditions[parent], s1, sim);
      }
      else
      {
        // this should be a 'pass' operation
        a = new NotSignal(Symid.NOSYMBOL, conditionSignal, sim);
      }
      newSignals.Add(a);
      conditions[transition] = a;
    }
  }
}
